﻿package com.teewe.utils
{
	import flash.utils.*;
	
	final public class ClassUtils
	{
		private static const IS_EXPORTED_CLASSES:Boolean = ClassExporter.includeClasses();
		private static const DEFINITION_CACHE:Dictionary = new Dictionary (true);
		
		public static function xmlToType (xml:XML):*
		{
			var type:String = xml.@type.toString();
			if (type == null || type == "")
				throw new Error ("type can't be empty");
			
			var definition:Class = getDefinitionByName(type) as Class;
			
			switch (definition)
			{
				case String:
					return xml.toString();
				case Boolean:
					return xml.toString().toLowerCase() == "true";
				case int:
				case uint:
				case Number:
				case Date:
					return new definition (xml.toString());
				case Array:
				case Object:
				case Dictionary:
					throw new Error ("type " + definition + " is not supported"); 
				default:
					return new definition (xml);
			}
		}
		
		public static function typeToXML (object:*, nodeName:String):XML
		{
			var typeValue:String, objectValue:*, propertyName:*;
			var typeName:String = ClassUtils.transformTypeName(getQualifiedClassName(object));
			
			/*
			If the object is primitive return a value node
			*/
			if (ClassUtils.isPrimitive(object) == true)
			{
				if (object is Date)
					objectValue = Utils.formatDate(object, "YYYY/MM/DD HH:JJ");
				else
					objectValue = object.toString();
					
				return <{nodeName} type={typeName}>{objectValue}</{nodeName}>;
			}
			
			/*
			The object is complex.
			*/
			var rootNode:XML = <{nodeName} type={typeName} />;
			
			// first handle dynamic properties (for items that extend Array)
			for (propertyName in object)
			{
				objectValue = object[propertyName];
				if (objectValue != null)
					rootNode.appendChild(ClassUtils.typeToXML(objectValue, propertyName));
			}
			
			// now handle properties which are in the class definition
			var propertyList:Array = ClassUtils.getPropertyList (object);
			for each (propertyName in propertyList)
			{
				objectValue = object[propertyName];
				
				if (objectValue != null)
					rootNode.appendChild(ClassUtils.typeToXML(objectValue, propertyName));
			}
			
			return rootNode;
		}
		
		public static function isPrimitive (object:*):Boolean
		{
			if (object is Number)
				return true;
			
			if (object is String)
				return true;
				
			if (object is Date)
				return true;
			
			if (object is Boolean)
				return true;
				
			return false;	
		}
		
		public static function getClassName (object:*):String
		{
			var fullName:String = getQualifiedClassName(object);
			var index:int = fullName.indexOf("::");
			
			if (index != -1)
				return fullName.slice(index + 2);
			else
				return fullName; 
		}
		/*
		This function caches the public properties for each of the objects [does NOT include getter/setter]. This saves calling describeType everytime you want to
		iterate over variables. It's used in dispose and in the typeToXML.
		*/
		public static function getPropertyList (object:*):Array
		{
			var list:Array = DEFINITION_CACHE[object.constructor];
			if (list == null)
			{
				list = [];
				
				var variableList:XMLList = describeType(object)..variable;
				var variableXML:XML
				
				for each (variableXML in variableList)
					list.push(variableXML.@name.toString());
					
				DEFINITION_CACHE[object.constructor] = list;
			}
			
			return list;
		}
		
		private static function transformTypeName (value:String):String
		{
			return value.replace ("::", ".");
		}
	}
}